---
title: Guía de Migración
description: Migra a la última versión estable de Bloc.
---

import { Code, Tabs, TabItem } from '@astrojs/starlight/components';

:::tip

Por favor, consulta el
[registro de versiones](https://github.com/felangel/bloc/releases) para obtener
más información sobre los cambios en cada versión.

:::

## v10.0.0

### `package:bloc_test`

#### ❗✨ Desacoplar `blocTest` de `BlocBase`

:::note[¿Qué cambió?]

En bloc_test v10.0.0, la API `blocTest` ya no está estrechamente acoplada a
`BlocBase`.

:::

##### Justificación

`blocTest` debería usar las interfaces principales de bloc cuando sea posible
para una mayor flexibilidad y reutilización. Anteriormente esto no era posible
porque `BlocBase` implementaba `StateStreamableSource`, lo cual no era
suficiente para `blocTest` debido a la dependencia interna en la API `emit`.

### `package:hydrated_bloc`

#### ❗✨ Soporte para WebAssembly

:::note[¿Qué cambió?]

En hydrated_bloc v10.0.0, se añadió soporte para compilar a WebAssembly (wasm).

:::

##### Justificación

Anteriormente no era posible compilar aplicaciones a wasm cuando se usaba
`hydrated_bloc`. En la versión v10.0.0, el paquete fue refactorizado para
permitir la compilación a wasm.

**v9.x.x**

```dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorage.webStorageDirectory
        : await getTemporaryDirectory(),
  );
  runApp(App());
}
```

**v10.x.x**

```dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorageDirectory.web
        : HydratedStorageDirectory((await getTemporaryDirectory()).path),
  );
  runApp(const App());
}
```

## v9.0.0

### `package:bloc`

#### ❗🧹 Eliminar APIs Obsoletas

:::note[¿Qué cambió?]

En bloc v9.0.0, todas las APIs previamente obsoletas fueron eliminadas.

:::

##### Resumen

- `BlocOverrides` eliminado en favor de `Bloc.observer` y `Bloc.transformer`

#### ❗✨ Introducir nueva Interfaz `EmittableStateStreamableSource`

:::note[¿Qué cambió?]

En bloc v9.0.0, se introdujo una nueva interfaz central
`EmittableStateStreamableSource`.

:::

##### Justificación

`package:bloc_test` estaba previamente estrechamente acoplado a `BlocBase`. La
interfaz `EmittableStateStreamableSource` se introdujo para permitir que
`blocTest` se desacople de la implementación concreta de `BlocBase`.

### `package:hydrated_bloc`

#### ✨ Reintroducir la API `HydratedBloc.storage`

:::note[¿Qué cambió?]

En hydrated_bloc v9.0.0, `HydratedBlocOverrides` fue eliminado en favor de la
API `HydratedBloc.storage`.

:::

##### Justificación

Consulta la
[justificación para reintroducir las anulaciones de Bloc.observer y Bloc.transformer](/es/migration#-reintroducir-las-apis-blocobserver-y-bloctransformer).

**v8.x.x**

```dart
Future<void> main() async {
  final storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorage.webStorageDirectory
        : await getTemporaryDirectory(),
  );
  HydratedBlocOverrides.runZoned(
    () => runApp(App()),
    storage: storage,
  );
}
```

**v9.0.0**

```dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorage.webStorageDirectory
        : await getTemporaryDirectory(),
  );
  runApp(App());
}
```

## v8.1.0

### `package:bloc`

#### ✨ Reintroducir las APIs `Bloc.observer` y `Bloc.transformer`

:::note[¿Qué cambió?]

En bloc v8.1.0, `BlocOverrides` fue deprecado en favor de las APIs
`Bloc.observer` y `Bloc.transformer`.

:::

##### Justificación

La API `BlocOverrides` se introdujo en v8.0.0 en un intento de soportar
configuraciones específicas de bloc como `BlocObserver`, `EventTransformer` y
`HydratedStorage`. En aplicaciones puras de Dart, los cambios funcionaron bien;
sin embargo, en aplicaciones Flutter la nueva API causó más problemas de los que
resolvió.

La API `BlocOverrides` se inspiró en APIs similares en Flutter/Dart:

- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)
- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)

**Problemas**

Aunque no fue la razón principal para estos cambios, la API `BlocOverrides`
introdujo complejidad adicional para los desarrolladores. Además de aumentar la
cantidad de anidamiento y líneas de código necesarias para lograr el mismo
efecto, la API `BlocOverrides` requería que los desarrolladores tuvieran un
sólido entendimiento de
[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) en Dart.
Las `Zones` no son un concepto amigable para principiantes y el no entender cómo
funcionan podría llevar a la introducción de errores (como observadores,
transformadores o instancias de almacenamiento no inicializadas).

Por ejemplo, muchos desarrolladores tendrían algo como:

```dart
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  BlocOverrides.runZoned(...);
}
```

El código anterior, aunque parece inofensivo, puede llevar a muchos errores
difíciles de rastrear. La zona desde la cual se llama inicialmente a
`WidgetsFlutterBinding.ensureInitialized` será la zona en la que se manejan los
eventos de gestos (por ejemplo, callbacks `onTap`, `onPressed`) debido a
`GestureBinding.initInstances`. Este es solo uno de los muchos problemas
causados por el uso de `zoneValues`.

Además, Flutter hace muchas cosas detrás de escena que implican
bifurcar/manipular Zonas (especialmente al ejecutar pruebas) lo que puede llevar
a comportamientos inesperados (y en muchos casos comportamientos que están fuera
del control del desarrollador -- ver problemas a continuación).

Debido al uso de
[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), la
transición a la API `BlocOverrides` llevó al descubrimiento de varios
errores/limitaciones en Flutter (específicamente alrededor de las Pruebas de
Widgets e Integración):

- https://github.com/flutter/flutter/issues/96939
- https://github.com/flutter/flutter/issues/94123
- https://github.com/flutter/flutter/issues/93676

lo cual afectó a muchos desarrolladores que usaban la biblioteca bloc:

- https://github.com/felangel/bloc/issues/3394
- https://github.com/felangel/bloc/issues/3350
- https://github.com/felangel/bloc/issues/3319

**v8.0.x**

```dart
void main() {
  BlocOverrides.runZoned(
    () {
      // ...
    },
    blocObserver: CustomBlocObserver(),
    eventTransformer: customEventTransformer(),
  );
}
```

**v8.1.0**

```dart
void main() {
  Bloc.observer = CustomBlocObserver();
  Bloc.transformer = customEventTransformer();

  // ...
}
```

## v8.0.0

### `package:bloc`

#### ❗✨ Introducir nueva API `BlocOverrides`

:::note[¿Qué cambió?]

En bloc v8.0.0, `Bloc.observer` y `Bloc.transformer` fueron eliminados en favor
de la API `BlocOverrides`.

:::

##### Justificación

La API anterior utilizada para sobrescribir el `BlocObserver` y
`EventTransformer` predeterminados dependía de un singleton global tanto para el
`BlocObserver` como para el `EventTransformer`.

Como resultado, no era posible:

- Tener múltiples implementaciones de `BlocObserver` o `EventTransformer`
  limitadas a diferentes partes de la aplicación.
- Tener sobrescrituras de `BlocObserver` o `EventTransformer` limitadas a un
  paquete.
  - Si un paquete dependía de `package:bloc` y registraba su propio
    `BlocObserver`, cualquier consumidor del paquete tendría que sobrescribir el
    `BlocObserver` del paquete o informar al `BlocObserver` del paquete.

También era más difícil de probar debido al estado global compartido entre las
pruebas.

Bloc v8.0.0 introduce una clase `BlocOverrides` que permite a los
desarrolladores sobrescribir `BlocObserver` y/o `EventTransformer` para una
`Zone` específica en lugar de depender de un singleton global mutable.

**v7.x.x**

```dart
void main() {
  Bloc.observer = CustomBlocObserver();
  Bloc.transformer = customEventTransformer();

  // ...
}
```

**v8.0.0**

```dart
void main() {
  BlocOverrides.runZoned(
    () {
      // ...
    },
    blocObserver: CustomBlocObserver(),
    eventTransformer: customEventTransformer(),
  );
}
```

Las instancias de `Bloc` usarán el `BlocObserver` y/o `EventTransformer` para la
`Zone` actual a través de `BlocOverrides.current`. Si no hay `BlocOverrides`
para la zona, usarán los valores predeterminados internos existentes (sin cambio
en comportamiento/funcionalidad).

Esto permite que cada `Zone` funcione de manera independiente con sus propios
`BlocOverrides`.

```dart
BlocOverrides.runZoned(
  () {
    // BlocObserverA y eventTransformerA
    final overrides = BlocOverrides.current;

    // Los Blocs en esta zona reportan a BlocObserverA
    // y usan eventTransformerA como el transformador predeterminado.
    // ...

    // Más tarde...
    BlocOverrides.runZoned(
      () {
        // BlocObserverB y eventTransformerB
        final overrides = BlocOverrides.current;

        // Los Blocs en esta zona reportan a BlocObserverB
        // y usan eventTransformerB como el transformador predeterminado.
        // ...
      },
      blocObserver: BlocObserverB(),
      eventTransformer: eventTransformerB(),
    );
  },
  blocObserver: BlocObserverA(),
  eventTransformer: eventTransformerA(),
);
```

#### ❗✨ Mejorar el Manejo y Reporte de Errores

:::note[¿Qué cambió?]

En bloc v8.0.0, `BlocUnhandledErrorException` se eliminó. Además, cualquier
excepción no capturada siempre se reporta a `onError` y se vuelve a lanzar
(independientemente del modo de depuración o lanzamiento). La API `addError`
informa errores a `onError`, pero no trata los errores reportados como
excepciones no capturadas.

:::

##### Justificación

El objetivo de estos cambios es:

- hacer que las excepciones internas no manejadas sean extremadamente obvias
  mientras se preserva la funcionalidad del bloc
- soportar `addError` sin interrumpir el flujo de control

Anteriormente, el manejo y reporte de errores variaba dependiendo de si la
aplicación se ejecutaba en modo de depuración o lanzamiento. Además, los errores
reportados a través de `addError` se trataban como excepciones no capturadas en
modo de depuración, lo que llevaba a una mala experiencia de desarrollador al
usar la API `addError` (específicamente al escribir pruebas unitarias).

En v8.0.0, `addError` se puede usar de manera segura para reportar errores y
`blocTest` se puede usar para verificar que los errores se reporten. Todos los
errores aún se reportan a `onError`, sin embargo, solo las excepciones no
capturadas se vuelven a lanzar (independientemente del modo de depuración o
lanzamiento).

#### ❗🧹 Hacer `BlocObserver` abstracto

:::note[¿Qué cambió?]

En bloc v8.0.0, `BlocObserver` se convirtió en una clase `abstract`, lo que
significa que no se puede instanciar una instancia de `BlocObserver`.

:::

##### Justificación

`BlocObserver` estaba destinado a ser una interfaz. Dado que la implementación
predeterminada de la API son operaciones nulas, `BlocObserver` es ahora una
clase `abstract` para comunicar claramente que la clase está destinada a ser
extendida y no instanciada directamente.

**v7.x.x**

```dart
void main() {
  // Era posible crear una instancia de la clase base.
  final observer = BlocObserver();
}
```

**v8.0.0**

```dart
class MyBlocObserver extends BlocObserver {...}

void main() {
  // No se puede instanciar la clase base.
  final observer = BlocObserver(); // ERROR

  // Extiende `BlocObserver` en su lugar.
  final observer = MyBlocObserver(); // OK
}
```

#### ❗✨ `add` lanza `StateError` si el Bloc está cerrado

:::note[¿Qué cambió?]

En bloc v8.0.0, llamar a `add` en un bloc cerrado resultará en un `StateError`.

:::

##### Justificación

Anteriormente, era posible llamar a `add` en un bloc cerrado y el error interno
se tragaba, lo que dificultaba depurar por qué el evento añadido no se estaba
procesando. Para hacer este escenario más visible, en v8.0.0, llamar a `add` en
un bloc cerrado lanzará un `StateError` que se informará como una excepción no
capturada y se propagará a `onError`.

#### ❗✨ `emit` lanza `StateError` si el Bloc está cerrado

:::note[¿Qué cambió?]

En bloc v8.0.0, llamar a `emit` dentro de un bloc cerrado resultará en un
`StateError`.

:::

##### Justificación

Anteriormente, era posible llamar a `emit` dentro de un bloc cerrado y no
ocurría ningún cambio de estado, pero tampoco había una indicación de lo que
salió mal, lo que dificultaba la depuración. Para hacer este escenario más
visible, en v8.0.0, llamar a `emit` dentro de un bloc cerrado lanzará un
`StateError` que se informará como una excepción no capturada y se propagará a
`onError`.

#### ❗🧹 Eliminar APIs Obsoletas

:::note[¿Qué cambió?]

En bloc v8.0.0, todas las APIs previamente obsoletas fueron eliminadas.

:::

##### Resumen

- `mapEventToState` eliminado en favor de `on<Event>`
- `transformEvents` eliminado en favor de la API `EventTransformer`
- `TransitionFunction` typedef eliminado en favor de la API `EventTransformer`
- `listen` eliminado en favor de `stream.listen`

### `package:bloc_test`

#### ✨ `MockBloc` y `MockCubit` ya no requieren `registerFallbackValue`

:::note[¿Qué cambió?]

En bloc_test v9.0.0, los desarrolladores ya no necesitan llamar explícitamente a
`registerFallbackValue` al usar `MockBloc` o `MockCubit`.

:::

##### Resumen

`registerFallbackValue` solo es necesario cuando se usa el matcher `any()` de
`package:mocktail` para un tipo personalizado. Anteriormente,
`registerFallbackValue` era necesario para cada `Event` y `State` al usar
`MockBloc` o `MockCubit`.

**v8.x.x**

```dart
class FakeMyEvent extends Fake implements MyEvent {}
class FakeMyState extends Fake implements MyState {}
class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}

void main() {
  setUpAll(() {
    registerFallbackValue(FakeMyEvent());
    registerFallbackValue(FakeMyState());
  });

  // Tests...
}
```

**v9.0.0**

```dart
class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}

void main() {
  // Tests...
}
```

### `package:hydrated_bloc`

#### ❗✨ Introducir nueva API `HydratedBlocOverrides`

:::note[¿Qué cambió?]

En hydrated_bloc v8.0.0, `HydratedBloc.storage` fue eliminado en favor de la API
`HydratedBlocOverrides`.

:::

##### Justificación

Anteriormente, se utilizaba un singleton global para sobrescribir la
implementación de `Storage`.

Como resultado, no era posible tener múltiples implementaciones de `Storage`
limitadas a diferentes partes de la aplicación. También era más difícil de
probar debido al estado global compartido entre las pruebas.

`HydratedBloc` v8.0.0 introduce una clase `HydratedBlocOverrides` que permite a
los desarrolladores sobrescribir `Storage` para una `Zone` específica en lugar
de depender de un singleton global mutable.

**v7.x.x**

```dart
void main() async {
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: await getApplicationSupportDirectory(),
  );

  // ...
}
```

**v8.0.0**

```dart
void main() {
  final storage = await HydratedStorage.build(
    storageDirectory: await getApplicationSupportDirectory(),
  );

  HydratedBlocOverrides.runZoned(
    () {
      // ...
    },
    storage: storage,
  );
}
```

`HydratedBloc` usará el `Storage` para la `Zone` actual a través de
`HydratedBlocOverrides.current`.

Esto permite que cada `Zone` funcione de manera independiente con sus propios
`BlocOverrides`.

## v7.2.0

### `package:bloc`

#### ✨ Introducir nueva API `on<Event>`

:::note[¿Qué cambió?]

En bloc v7.2.0, `mapEventToState` fue deprecado en favor de `on<Event>`.
`mapEventToState` será eliminado en bloc v8.0.0.

:::

##### Justificación

La API `on<Event>` se introdujo como parte de
[[Propuesta] Reemplazar mapEventToState con on\<Event\> en Bloc](https://github.com/felangel/bloc/issues/2526).
Debido a [un problema en Dart](https://github.com/dart-lang/sdk/issues/44616) no
siempre es obvio cuál será el valor de `state` cuando se trata de generadores
asincrónicos anidados (`async*`). Aunque hay formas de solucionar el problema,
uno de los principios fundamentales de la biblioteca bloc es ser predecible. La
API `on<Event>` se creó para hacer que la biblioteca sea lo más segura posible
de usar y para eliminar cualquier incertidumbre en lo que respecta a los cambios
de estado.

:::tip

Para más información,
[lee la propuesta completa](https://github.com/felangel/bloc/issues/2526).

:::

**Resumen**

`on<E>` te permite registrar un manejador de eventos para todos los eventos del
tipo `E`. Por defecto, los eventos se procesarán concurrentemente cuando se use
`on<E>` en lugar de `mapEventToState`, que procesa los eventos
`secuencialmente`.

**v7.1.0**

```dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    if (event is Increment) {
      yield state + 1;
    }
  }
}
```

**v7.2.0**

```dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}
```

:::note

Cada `EventHandler` registrado funciona de manera independiente, por lo que es
importante registrar manejadores de eventos según el tipo de transformador que
desees aplicar.

:::

Si deseas mantener el mismo comportamiento exacto que en la versión v7.1.0,
puedes registrar un solo manejador de eventos para todos los eventos y aplicar
un transformador `sequential`:

```dart
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyState()) {
    on<MyEvent>(_onEvent, transformer: sequential())
  }

  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {
    // TODO: logic goes here...
  }
}
```

También puedes sobrescribir el `EventTransformer` predeterminado para todos los
blocs en tu aplicación:

```dart
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

void main() {
  Bloc.transformer = sequential<dynamic>();
  ...
}
```

#### ✨ Introducir nueva API `EventTransformer`

:::note[¿Qué cambió?]

En bloc v7.2.0, `transformEvents` fue deprecado en favor de la API
`EventTransformer`. `transformEvents` será eliminado en bloc v8.0.0.

:::

##### Justificación

La API `on<Event>` abrió la puerta para poder proporcionar un transformador de
eventos personalizado por manejador de eventos. Se introdujo un nuevo typedef
`EventTransformer` que permite a los desarrolladores transformar el flujo de
eventos entrantes para cada manejador de eventos en lugar de tener que
especificar un único transformador de eventos para todos los eventos.

**Resumen**

Un `EventTransformer` es responsable de tomar el flujo entrante de eventos junto
con un `EventMapper` (tu manejador de eventos) y devolver un nuevo flujo de
eventos.

```dart
typedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)
```

El `EventTransformer` predeterminado procesa todos los eventos concurrentemente
y se ve algo así:

```dart
EventTransformer<E> concurrent<E>() {
  return (events, mapper) => events.flatMap(mapper);
}
```

:::tip

Consulta [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency)
para un conjunto de transformadores de eventos personalizados y con opiniones.

:::

**v7.1.0**

```dart
@override
Stream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {
  return events
    .debounceTime(const Duration(milliseconds: 300))
    .flatMap(transitionFn);
}
```

**v7.2.0**

```dart
/// Define un `EventTransformer` personalizado
EventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {
  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
}

MyBloc() : super(MyState()) {
  /// Aplica el `EventTransformer` personalizado al `EventHandler`
  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))
}
```

#### ⚠️ Marcar como obsoleta la API `transformTransitions`

:::note[¿Qué cambió?]

En bloc v7.2.0, `transformTransitions` fue deprecado en favor de sobrescribir la
API `stream`. `transformTransitions` será eliminado en bloc v8.0.0.

:::

##### Justificación

El getter `stream` en `Bloc` facilita la sobrescritura del flujo de estados
salientes, por lo tanto, ya no es valioso mantener una API
`transformTransitions` separada.

**Resumen**

**v7.1.0**

```dart
@override
Stream<Transition<Event, State>> transformTransitions(
  Stream<Transition<Event, State>> transitions,
) {
  return transitions.debounceTime(const Duration(milliseconds: 42));
}
```

**v7.2.0**

```dart
@override
Stream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));
```

## v7.0.0

### `package:bloc`

#### ❗ Bloc y Cubit extienden BlocBase

##### Justificación

Como desarrollador, la relación entre blocs y cubits era un poco incómoda.
Cuando se introdujo cubit por primera vez, comenzó como la clase base para
blocs, lo cual tenía sentido porque tenía un subconjunto de la funcionalidad y
los blocs simplemente extenderían Cubit y definirían APIs adicionales. Esto
tenía algunos inconvenientes:

- Todas las APIs tendrían que ser renombradas para aceptar un cubit por
  precisión o tendrían que mantenerse como bloc por consistencia, aunque
  jerárquicamente no fuera preciso
  ([#1708](https://github.com/felangel/bloc/issues/1708),
  [#1560](https://github.com/felangel/bloc/issues/1560)).

- Cubit tendría que extender Stream e implementar EventSink para tener una base
  común sobre la cual se puedan implementar widgets como BlocBuilder,
  BlocListener, etc. ([#1429](https://github.com/felangel/bloc/issues/1429)).

Más tarde, experimentamos con invertir la relación y hacer que bloc fuera la
clase base, lo que resolvió parcialmente el primer punto anterior pero introdujo
otros problemas:

- La API de cubit está sobrecargada debido a las APIs subyacentes de bloc como
  mapEventToState, add, etc.
  ([#2228](https://github.com/felangel/bloc/issues/2228))
  - Los desarrolladores técnicamente pueden invocar estas APIs y romper cosas.
- Todavía tenemos el mismo problema de cubit exponiendo toda la API de stream
  como antes ([#1429](https://github.com/felangel/bloc/issues/1429))

Para abordar estos problemas, introdujimos una clase base tanto para `Bloc` como
para `Cubit` llamada `BlocBase` para que los componentes upstream puedan seguir
interoperando con instancias de bloc y cubit sin exponer toda la API de `Stream`
y `EventSink` directamente.

**Resumen**

**BlocObserver**

**v6.1.x**

```dart
class SimpleBlocObserver extends BlocObserver {
  @override
  void onCreate(Cubit cubit) {...}

  @override
  void onEvent(Bloc bloc, Object event) {...}

  @override
  void onChange(Cubit cubit, Object event) {...}

  @override
  void onTransition(Bloc bloc, Transition transition) {...}

  @override
  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}

  @override
  void onClose(Cubit cubit) {...}
}
```

**v7.0.0**

```dart
class SimpleBlocObserver extends BlocObserver {
  @override
  void onCreate(BlocBase bloc) {...}

  @override
  void onEvent(Bloc bloc, Object event) {...}

  @override
  void onChange(BlocBase bloc, Object? event) {...}

  @override
  void onTransition(Bloc bloc, Transition transition) {...}

  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}

  @override
  void onClose(BlocBase bloc) {...}
}
```

**Bloc/Cubit**

**v6.1.x**

```dart
final bloc = MyBloc();
bloc.listen((state) {...});

final cubit = MyCubit();
cubit.listen((state) {...});
```

**v7.0.0**

```dart
final bloc = MyBloc();
bloc.stream.listen((state) {...});

final cubit = MyCubit();
cubit.stream.listen((state) {...});
```

### `package:bloc_test`

#### ❗seed devuelve una función para soportar valores dinámicos

##### Justificación

Para soportar tener un valor de semilla mutable que se pueda actualizar
dinámicamente en `setUp`, `seed` devuelve una función.

**Resumen**

**v7.x.x**

```dart
blocTest(
  '...',
  seed: MyState(),
  ...
);
```

**v8.0.0**

```dart
blocTest(
  '...',
  seed: () => MyState(),
  ...
);
```

#### ❗expect devuelve una función para soportar valores dinámicos y soporte de matchers

##### Justificación

Para soportar tener una expectativa mutable que se pueda actualizar
dinámicamente en `setUp`, `expect` devuelve una función. `expect` también
soporta `Matchers`.

**Resumen**

**v7.x.x**

```dart
blocTest(
  '...',
  expect: [MyStateA(), MyStateB()],
  ...
);
```

**v8.0.0**

```dart
blocTest(
  '...',
  expect: () => [MyStateA(), MyStateB()],
  ...
);

// It can also be a `Matcher`
blocTest(
  '...',
  expect: () => contains(MyStateA()),
  ...
);
```

#### ❗errors devuelve una función para soportar valores dinámicos y soporte de matchers

##### Justificación

Para soportar tener un valor de errores mutable que se pueda actualizar
dinámicamente en `setUp`, `errors` devuelve una función. `errors` también
soporta `Matchers`.

**Resumen**

**v7.x.x**

```dart
blocTest(
  '...',
  errors: [MyError()],
  ...
);
```

**v8.0.0**

```dart
blocTest(
  '...',
  errors: () => [MyError()],
  ...
);

// It can also be a `Matcher`
blocTest(
  '...',
  errors: () => contains(MyError()),
  ...
);
```

#### ❗MockBloc y MockCubit

##### Justificación

Para soportar la simulación de varias APIs centrales, `MockBloc` y `MockCubit`
se exportan como parte del paquete `bloc_test`. Anteriormente, `MockBloc` tenía
que ser utilizado tanto para instancias de `Bloc` como de `Cubit`, lo cual no
era intuitivo.

**Resumen**

**v7.x.x**

```dart
class MockMyBloc extends MockBloc<MyState> implements MyBloc {}
class MockMyCubit extends MockBloc<MyState> implements MyBloc {}
```

**v8.0.0**

```dart
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
class MockMyCubit extends MockCubit<MyState> implements MyCubit {}
```

#### ❗Integración con Mocktail

##### Justificación

Debido a varias limitaciones de la versión null-safe del paquete
[package:mockito](https://pub.dev/packages/mockito) descritas
[aquí](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),
el paquete [package:mocktail](https://pub.dev/packages/mocktail) es utilizado
por `MockBloc` y `MockCubit`. Esto permite a los desarrolladores continuar
usando una API de simulación familiar sin la necesidad de escribir stubs
manualmente o depender de la generación de código.

**Resumen**

**v7.x.x**

```dart
import 'package:mockito/mockito.dart';

...

when(bloc.state).thenReturn(MyState());
verify(bloc.add(any)).called(1);
```

**v8.0.0**

```dart
import 'package:mocktail/mocktail.dart';

...

when(() => bloc.state).thenReturn(MyState());
verify(() => bloc.add(any())).called(1);
```

> Please refer to [#347](https://github.com/dart-lang/mockito/issues/347) as
> well as the
> [mocktail documentation](https://github.com/felangel/mocktail/tree/main/packages/mocktail)
> for more information.

### `package:flutter_bloc`

#### ❗ renombrar el parámetro `cubit` a `bloc`

##### Justificación

Como resultado de la refactorización en `package:bloc` para introducir
`BlocBase`, que extiende `Bloc` y `Cubit`, los parámetros de `BlocBuilder`,
`BlocConsumer` y `BlocListener` se renombraron de `cubit` a `bloc` porque los
widgets operan sobre el tipo `BlocBase`. Esto también se alinea aún más con el
nombre de la biblioteca y, con suerte, mejora la legibilidad.

**Resumen**

**v6.1.x**

```dart
BlocBuilder(
  cubit: myBloc,
  ...
)

BlocListener(
  cubit: myBloc,
  ...
)

BlocConsumer(
  cubit: myBloc,
  ...
)
```

**v7.0.0**

```dart
BlocBuilder(
  bloc: myBloc,
  ...
)

BlocListener(
  bloc: myBloc,
  ...
)

BlocConsumer(
  bloc: myBloc,
  ...
)
```

### `package:hydrated_bloc`

#### ❗storageDirectory es requerido al llamar a HydratedStorage.build

##### Justificación

Para hacer que `package:hydrated_bloc` sea un paquete puro de Dart, se eliminó
la dependencia de
[package:path_provider](https://pub.dev/packages/path_provider) y el parámetro
`storageDirectory` al llamar a `HydratedStorage.build` es requerido y ya no
tiene como valor predeterminado `getTemporaryDirectory`.

**Resumen**

**v6.x.x**

```dart
HydratedBloc.storage = await HydratedStorage.build();
```

**v7.0.0**

```dart
import 'package:path_provider/path_provider.dart';

...

HydratedBloc.storage = await HydratedStorage.build(
  storageDirectory: await getTemporaryDirectory(),
);
```

## v6.1.0

### `package:flutter_bloc`

#### ❗context.bloc y context.repository están obsoletos en favor de context.read y context.watch

##### Justificación

`context.read`, `context.watch` y `context.select` se añadieron para alinearse
con la API existente de [provider](https://pub.dev/packages/provider) con la que
muchos desarrolladores están familiarizados y para abordar problemas planteados
por la comunidad. Para mejorar la seguridad del código y mantener la
consistencia, `context.bloc` se deprecó porque puede ser reemplazado por
`context.read` o `context.watch` dependiendo de si se usa directamente dentro de
`build`.

**context.watch**

`context.watch` aborda la solicitud de tener un
[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) porque podemos
observar varios blocs dentro de un solo `Builder` para renderizar la UI basada
en múltiples estados:

```dart
Builder(
  builder: (context) {
    final stateA = context.watch<BlocA>().state;
    final stateB = context.watch<BlocB>().state;
    final stateC = context.watch<BlocC>().state;

    // return a Widget which depends on the state of BlocA, BlocB, and BlocC
  }
);
```

**context.select**

`context.select` permite a los desarrolladores renderizar/actualizar la UI
basada en una parte del estado de un bloc y aborda la solicitud de tener un
[buildWhen más simple](https://github.com/felangel/bloc/issues/1521).

```dart
final name = context.select((UserBloc bloc) => bloc.state.user.name);
```

El fragmento anterior nos permite acceder y reconstruir el widget solo cuando
cambia el nombre del usuario actual.

**context.read**

Aunque parece que `context.read` es idéntico a `context.bloc`, hay algunas
diferencias sutiles pero significativas. Ambos permiten acceder a un bloc con un
`BuildContext` y no resultan en reconstrucciones; sin embargo, `context.read` no
se puede llamar directamente dentro de un método `build`. Hay dos razones
principales para usar `context.bloc` dentro de `build`:

1. **Para acceder al estado del bloc**

```dart
@override
Widget build(BuildContext context) {
  final state = context.bloc<MyBloc>().state;
  return Text('$state');
}
```

El uso anterior es propenso a errores porque el widget `Text` no se reconstruirá
si el estado del bloc cambia. En este escenario, se debe usar un `BlocBuilder` o
`context.watch`.

```dart
@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
  return Text('$state');
}
```

or

```dart
@override
Widget build(BuildContext context) {
  return BlocBuilder<MyBloc, MyState>(
    builder: (context, state) => Text('$state'),
  );
}
```

:::note

Usar `context.watch` en la raíz del método `build` resultará en que todo el
widget se reconstruya cuando cambie el estado del bloc. Si no es necesario
reconstruir todo el widget, usa `BlocBuilder` para envolver las partes que deben
reconstruirse, usa un `Builder` con `context.watch` para delimitar las
reconstrucciones, o descompón el widget en widgets más pequeños.

:::

2. **Para acceder al bloc y poder agregar un evento**

```dart
@override
Widget build(BuildContext context) {
  final bloc = context.bloc<MyBloc>();
  return ElevatedButton(
    onPressed: () => bloc.add(MyEvent()),
    ...
  )
}
```

El uso anterior es ineficiente porque resulta en una búsqueda del bloc en cada
reconstrucción cuando el bloc solo es necesario cuando el usuario toca el
`ElevatedButton`. En este escenario, es preferible usar `context.read` para
acceder al bloc directamente donde se necesita (en este caso, en el callback
`onPressed`).

```dart
@override
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: () => context.read<MyBloc>().add(MyEvent()),
    ...
  )
}
```

**Resumen**

**v6.0.x**

```dart
@override
Widget build(BuildContext context) {
  final bloc = context.bloc<MyBloc>();
  return ElevatedButton(
    onPressed: () => bloc.add(MyEvent()),
    ...
  )
}
```

**v6.1.x**

```dart
@override
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: () => context.read<MyBloc>().add(MyEvent()),
    ...
  )
}
```

?> Si accedes a un bloc para agregar un evento, realiza el acceso al bloc usando
`context.read` en el callback donde se necesita.

**v6.0.x**

```dart
@override
Widget build(BuildContext context) {
  final state = context.bloc<MyBloc>().state;
  return Text('$state');
}
```

**v6.1.x**

```dart
@override
Widget build(BuildContext context) {
  final state = context.watch<MyBloc>().state;
  return Text('$state');
}
```

?> Usa `context.watch` cuando accedas al estado del bloc para asegurar que el
widget se reconstruya cuando el estado cambie.

## v6.0.0

### `package:bloc`

#### ❗BlocObserver onError toma Cubit

##### Justificación

Debido a la integración de `Cubit`, `onError` ahora se comparte entre las
instancias de `Bloc` y `Cubit`. Dado que `Cubit` es la base, `BlocObserver`
aceptará un tipo `Cubit` en lugar de un tipo `Bloc` en la sobrescritura de
`onError`.

**v5.x.x**

```dart
class MyBlocObserver extends BlocObserver {
  @override
  void onError(Bloc bloc, Object error, StackTrace stackTrace) {
    super.onError(bloc, error, stackTrace);
  }
}
```

**v6.0.0**

```dart
class MyBlocObserver extends BlocObserver {
  @override
  void onError(Cubit cubit, Object error, StackTrace stackTrace) {
    super.onError(cubit, error, stackTrace);
  }
}
```

#### ❗Bloc no emite el último estado en la suscripción

##### Justificación

Este cambio se realizó para alinear `Bloc` y `Cubit` con el comportamiento
incorporado de `Stream` en `Dart`. Además, conformar este comportamiento antiguo
en el contexto de `Cubit` llevó a muchos efectos secundarios no deseados y, en
general, complicó innecesariamente las implementaciones internas de otros
paquetes como `flutter_bloc` y `bloc_test` (requiriendo `skip(1)`, etc...).

**v5.x.x**

```dart
final bloc = MyBloc();
bloc.listen(print);
```

Anteriormente, el fragmento anterior mostraría el estado inicial del bloc
seguido de los cambios de estado posteriores.

**v6.x.x**

En v6.0.0, el fragmento anterior no muestra el estado inicial y solo muestra los
cambios de estado posteriores. El comportamiento anterior se puede lograr con lo
siguiente:

```dart
final bloc = MyBloc();
print(bloc.state);
bloc.listen(print);
```

?> **Nota**: Este cambio solo afectará al código que dependa de suscripciones
directas a blocs. Al usar `BlocBuilder`, `BlocListener` o `BlocConsumer` no
habrá ningún cambio notable en el comportamiento.

### `package:bloc_test`

#### ❗MockBloc solo requiere el tipo de Estado

##### Justificación

No es necesario y elimina código adicional, además de hacer que `MockBloc` sea
compatible con `Cubit`.

**v5.x.x**

```dart
class MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}
```

**v6.0.0**

```dart
class MockCounterBloc extends MockBloc<int> implements CounterBloc {}
```

#### ❗whenListen solo requiere el tipo de Estado

##### Justificación

No es necesario y elimina código adicional, además de hacer que `whenListen` sea
compatible con `Cubit`.

**v5.x.x**

```dart
whenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));
```

**v6.0.0**

```dart
whenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));
```

#### ❗blocTest does not require Event type

##### Justificación

No es necesario y elimina código adicional, además de hacer que `blocTest` sea
compatible con `Cubit`.

**v5.x.x**

```dart
blocTest<CounterBloc, CounterEvent, int>(
  'emits [1] when increment is called',
  build: () async => CounterBloc(),
  act: (bloc) => bloc.add(CounterEvent.increment),
  expect: const <int>[1],
);
```

**v6.0.0**

```dart
blocTest<CounterBloc, int>(
  'emits [1] when increment is called',
  build: () => CounterBloc(),
  act: (bloc) => bloc.add(CounterEvent.increment),
  expect: const <int>[1],
);
```

#### ❗blocTest skip por defecto es 0

##### Justificación

Dado que las instancias de `bloc` y `cubit` ya no emitirán el último estado para
nuevas suscripciones, ya no era necesario que `skip` tuviera un valor
predeterminado de `1`.

**v5.x.x**

```dart
blocTest<CounterBloc, CounterEvent, int>(
  'emits [0] when skip is 0',
  build: () async => CounterBloc(),
  skip: 0,
  expect: const <int>[0],
);
```

**v6.0.0**

```dart
blocTest<CounterBloc, int>(
  'emits [] when skip is 0',
  build: () => CounterBloc(),
  skip: 0,
  expect: const <int>[],
);
```

El estado inicial de un bloc o cubit se puede probar con lo siguiente:

```dart
test('initial state is correct', () {
  expect(MyBloc().state, InitialState());
});
```

#### ❗blocTest hacer que build sea síncrono

##### Justificación

Anteriormente, `build` se hizo `async` para que se pudieran realizar varias
preparaciones para poner el bloc bajo prueba en un estado específico. Ya no es
necesario y también resuelve varios problemas debido a la latencia añadida entre
la construcción y la suscripción internamente. En lugar de hacer una preparación
asincrónica para poner un bloc en un estado deseado, ahora podemos establecer el
estado del bloc encadenando `emit` con el estado deseado.

**v5.x.x**

```dart
blocTest<CounterBloc, CounterEvent, int>(
  'emits [2] when increment is added',
  build: () async {
    final bloc = CounterBloc();
    bloc.add(CounterEvent.increment);
    await bloc.take(2);
    return bloc;
  }
  act: (bloc) => bloc.add(CounterEvent.increment),
  expect: const <int>[2],
);
```

**v6.0.0**

```dart
blocTest<CounterBloc, int>(
  'emits [2] when increment is added',
  build: () => CounterBloc()..emit(1),
  act: (bloc) => bloc.add(CounterEvent.increment),
  expect: const <int>[2],
);
```

:::note

`emit` solo es visible para pruebas y nunca debe usarse fuera de las pruebas.

:::

### `package:flutter_bloc`

#### ❗El parámetro `bloc` de BlocBuilder se renombró a `cubit`

##### Justificación

Para que `BlocBuilder` pueda interoperar con instancias de `bloc` y `cubit`, el
parámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).

**v5.x.x**

```dart
BlocBuilder(
  bloc: myBloc,
  builder: (context, state) {...}
)
```

**v6.0.0**

```dart
BlocBuilder(
  cubit: myBloc,
  builder: (context, state) {...}
)
```

#### ❗BlocListener parámetro bloc renombrado a cubit

##### Justificación

Para que `BlocListener` pueda interoperar con instancias de `bloc` y `cubit`, el
parámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).

**v5.x.x**

```dart
BlocListener(
  bloc: myBloc,
  listener: (context, state) {...}
)
```

**v6.0.0**

```dart
BlocListener(
  cubit: myBloc,
  listener: (context, state) {...}
)
```

#### ❗BlocConsumer parámetro bloc renombrado a cubit

##### Justificación

Para que `BlocConsumer` pueda interoperar con instancias de `bloc` y `cubit`, el
parámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).

**v5.x.x**

```dart
BlocConsumer(
  bloc: myBloc,
  listener: (context, state) {...},
  builder: (context, state) {...}
)
```

**v6.0.0**

```dart
BlocConsumer(
  cubit: myBloc,
  listener: (context, state) {...},
  builder: (context, state) {...}
)
```

---

## v5.0.0

### `package:bloc`

#### ❗initialState ha sido eliminado

##### Justificación

Como desarrollador, tener que sobrescribir `initialState` al crear un bloc
presenta dos problemas principales:

- El `initialState` del bloc puede ser dinámico y también puede ser referenciado
  en un momento posterior (incluso fuera del propio bloc). De alguna manera,
  esto puede verse como una filtración de información interna del bloc a la capa
  de UI.
- Es verboso.

**v4.x.x**

```dart
class CounterBloc extends Bloc<CounterEvent, int> {
  @override
  int get initialState => 0;

  ...
}
```

**v5.0.0**

```dart
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  ...
}
```

?> Para más información, consulta
[#1304](https://github.com/felangel/bloc/issues/1304)

#### ❗BlocDelegate renombrado a BlocObserver

##### Justificación

El nombre `BlocDelegate` no era una descripción precisa del papel que
desempeñaba la clase. `BlocDelegate` sugiere que la clase juega un papel activo,
mientras que en realidad el papel previsto del `BlocDelegate` era ser un
componente pasivo que simplemente observa todos los blocs en una aplicación.

:::note

Idealmente, no debería haber ninguna funcionalidad o características orientadas
al usuario manejadas dentro de `BlocObserver`.

:::

**v4.x.x**

```dart
class MyBlocDelegate extends BlocDelegate {
  ...
}
```

**v5.0.0**

```dart
class MyBlocObserver extends BlocObserver {
  ...
}
```

#### ❗BlocSupervisor ha sido eliminado

##### Justificación

`BlocSupervisor` era otro componente que los desarrolladores debían conocer e
interactuar con el único propósito de especificar un `BlocDelegate`
personalizado. Con el cambio a `BlocObserver`, sentimos que mejoraba la
experiencia del desarrollador al establecer el observador directamente en el
propio bloc.

?> Este cambio también nos permitió desacoplar otros complementos de bloc como
`HydratedStorage` del `BlocObserver`.

**v4.x.x**

```dart
BlocSupervisor.delegate = MyBlocDelegate();
```

**v5.0.0**

```dart
Bloc.observer = MyBlocObserver();
```

### `package:flutter_bloc`

#### ❗BlocBuilder condición renombrada a buildWhen

##### Justificación

Cuando se usa `BlocBuilder`, anteriormente podíamos especificar una `condición`
para determinar si el `builder` debería reconstruirse.

```dart
BlocBuilder<MyBloc, MyState>(
  buildWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al builder
  },
  builder: (context, state) {...}
)
```

El nombre `condition` no es muy autoexplicativo u obvio y, más importante aún,
cuando se interactúa con un `BlocConsumer`, la API se vuelve inconsistente
porque los desarrolladores pueden proporcionar dos condiciones (una para
`builder` y otra para `listener`). Como resultado, la API de `BlocConsumer`
expone un `buildWhen` y `listenWhen`.

```dart
BlocConsumer<MyBloc, MyState>(
  listenWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al listener
  },
  listener: (context, state) {...},
  buildWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al builder
  },
  builder: (context, state) {...},
)
```

Para alinear la API y proporcionar una experiencia de desarrollador más
consistente, `condition` fue renombrado a `buildWhen`.

**v4.x.x**

```dart
BlocBuilder<MyBloc, MyState>(
  buildWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al builder
  },
  builder: (context, state) {...}
)
```

**v5.0.0**

```dart
BlocBuilder<MyBloc, MyState>(
  buildWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al builder
  },
  builder: (context, state) {...}
)
```

#### ❗BlocListener condición renombrada a listenWhen

##### Justificación

Por las mismas razones descritas anteriormente, la condición de `BlocListener`
también fue renombrada.

**v4.x.x**

```dart
BlocListener<MyBloc, MyState>(
  listenWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al listener
  },
  listener: (context, state) {...}
)
```

**v5.0.0**

```dart
BlocListener<MyBloc, MyState>(
  listenWhen: (anterior, actual) {
    // devuelve true/false para determinar si se debe llamar al listener
  },
  listener: (context, state) {...}
)
```

### `package:hydrated_bloc`

#### ❗HydratedStorage y HydratedBlocStorage renombrados

##### Justificación

Para mejorar la reutilización del código entre
[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) y
[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), la implementación
concreta predeterminada de almacenamiento se renombró de `HydratedBlocStorage` a
`HydratedStorage`. Además, la interfaz `HydratedStorage` se renombró de
`HydratedStorage` a `Storage`.

**v4.0.0**

```dart
class MyHydratedStorage implements HydratedStorage {
  ...
}
```

**v5.0.0**

```dart
class MyHydratedStorage implements Storage {
  ...
}
```

#### ❗HydratedStorage desacoplado de BlocDelegate

##### Justificación

Como se mencionó anteriormente, `BlocDelegate` fue renombrado a `BlocObserver` y
se estableció directamente como parte del `bloc` a través de:

```dart
Bloc.observer = MyBlocObserver();
```

El siguiente cambio se realizó para:

- Mantener la consistencia con la nueva API de observador de bloc
- Mantener el almacenamiento limitado solo a `HydratedBloc`
- Desacoplar el `BlocObserver` del `Storage`

**v4.0.0**

```dart
BlocSupervisor.delegate = await HydratedBlocDelegate.build();
```

**v5.0.0**

```dart
HydratedBloc.storage = await HydratedStorage.build();
```

#### ❗Inicialización Simplificada

##### Justificación

Anteriormente, los desarrolladores tenían que llamar manualmente a
`super.initialState ?? DefaultInitialState()` para configurar sus instancias de
`HydratedBloc`. Esto es torpe y verboso y también incompatible con los cambios
importantes en `initialState` en `bloc`. Como resultado, en la versión v5.0.0,
la inicialización de `HydratedBloc` es idéntica a la inicialización normal de
`Bloc`.

**v4.0.0**

```dart
class CounterBloc extends HydratedBloc<CounterEvent, int> {
  @override
  int get initialState => super.initialState ?? 0;
}
```

**v5.0.0**

```dart
class CounterBloc extends HydratedBloc<CounterEvent, int> {
  CounterBloc() : super(0);

  ...
}
```
